﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace GPLib
{
    public partial class GpForm : Form
    {

        private const int WAIT_TIMEOUT = 3000;

        private readonly List< char > _presses; //monitors all keystrokes that occur on the form in order to provide delta analysis
        private readonly GarbagePumpEngine _gpe;

        private readonly char _enter;
        private readonly char _escape;

        public delegate void DStringCallback( string s );
        private readonly DStringCallback _stringcallback;

        //This is really for debugging but it makes for a handy display of what's actually taking place.
        public delegate void DPositionsCallback( List< int > positions );
        private readonly DPositionsCallback _positioncallback;

        /// <summary>
        /// Creates a form that manages user input with a Garbage Pump.
        /// </summary>
        /// <param name="stringcallback"></param>
        /// <param name="unmodifiedKeys">Keys that require no modification keys, or else you'll get garbage in return.</param>
        public GpForm( DStringCallback stringcallback, char[] unmodifiedKeys )
            : this( stringcallback, unmodifiedKeys, null, null )
        {
        }

        /// <summary>
        /// Experimental! Using the shift keys can cause shifted characters where there ought not to be.  You can use null in place of shiftKeys.
        /// </summary>
        /// <param name="stringcallback"></param>
        /// <param name="unmodifiedKeys"></param>
        /// <param name="shiftKeys">Can be null</param>
        public GpForm( DStringCallback stringcallback, char[] unmodifiedKeys, char[] shiftKeys )
            : this( stringcallback, unmodifiedKeys, shiftKeys, null )
        {
        }

        /// <summary>
        /// Experimental! Using the shift keys can cause shifted characters where there ought not to be.
        /// </summary>
        /// <param name="stringcallback"></param>
        /// <param name="unmodifiedKeys"></param>
        /// <param name="shiftKeys">Can be null</param>
        /// <param name="positionscallback">Debugging and demonstration callback</param>
        public GpForm( DStringCallback stringcallback, 
                       char[] unmodifiedKeys, 
                       char[] shiftKeys, 
                       DPositionsCallback positionscallback )
        {

            InitializeComponent();

            _presses = new List< char >();
            _gpe = new GarbagePumpEngine( GpOnError, unmodifiedKeys, shiftKeys );
            
            //simplifications
            _enter = ( char )Keys.Enter;
            _escape = ( char )Keys.Escape;

            //callbacks
            _stringcallback = stringcallback;
            _positioncallback = positionscallback;

            //tboxDummy cannot be viewed.  It's used to take the focus when the GP needs to be stopped.
            tboxDummy.Focus();
            tboxDummy.Select( 0, 0 );

        }

        private void GpOnError( string errormsg, bool isFatal )
        {

            if ( InvokeRequired )
                Invoke( new GarbagePumpEngine.DOnError( GpOnError ), new object[] { errormsg, isFatal } );
            else
            {
                MessageBox.Show( this, errormsg, "An error has occurred", MessageBoxButtons.OK, MessageBoxIcon.Error );

                if ( isFatal )
                    Close();
                else
                    AppLostFocus( false );
            }

        }

        private void StartGarbagePump()
        {

            bool failed = false;

            //Block user input until the GP can begin.
            using ( new SafeBlock( this ) )
            {

                if ( !_gpe.AsyncStart( WAIT_TIMEOUT ) )
                {
                    MessageBox.Show( this, 
                                     @"Unable to start the Garbage Pump.", 
                                     @"Failure to start error",
                                     MessageBoxButtons.OK,
                                     MessageBoxIcon.Error );

                    failed = true;
                }

            }

            if ( failed )
                Close();

        }

        private void ApplicationKeyMonitor( KeyPressEventArgs e )
        {

            _presses.Add( e.KeyChar );

            if ( e.KeyChar == _enter )
            {
                if ( _gpe.AsyncStop( WAIT_TIMEOUT ) )
                {
                    tboxDummy.Focus(); //switch focus out of the textbox
                    _stringcallback( StringDeltaAnalysis.GetUserText( _presses, _gpe.RandomKeys ) );

                    if ( _positioncallback != null )
                        _positioncallback( StringDeltaAnalysis.GetUserTextPositions( _presses, _gpe.RandomKeys, true, 5 ) );
                }
                else
                    MessageBox.Show( this, "Unable to retrieve password.  Garbage pump failed to stop.",
                                     "Failure to stop error", MessageBoxButtons.OK, MessageBoxIcon.Error );

                Close();

            }
            else if ( e.KeyChar == _escape )
            {
                //Empty is used to indicate that no string was provided and the library has closed
                if ( _gpe.AsyncStop( WAIT_TIMEOUT ) )
                    _stringcallback( String.Empty );

                else
                    MessageBox.Show( this, "Unable to retrieve password.  Garbage pump failed to stop.",
                                     "Failure to stop error", MessageBoxButtons.OK, MessageBoxIcon.Error );
                Close();
            }

        }

        private void AppLostFocus( bool close )
        {

            if ( _gpe.AsyncStop( WAIT_TIMEOUT ) )
            {

                Text = @"Inactive. Click on the textbox to start.";
                tboxDummy.Focus();
                tboxDummy.Select( 0, 0 );
                tboxInput.Text = @"[click here to type password]";

                if ( close )
                    Close();
               
            }
            else
                MessageBox.Show( this, "Unable to retrieve password.  Garbage pump failed to stop.",
                                    "Failure to stop error", MessageBoxButtons.OK, MessageBoxIcon.Error );

        }

        private void TboxInputEnter( object sender, EventArgs e )
        {

            if ( !_gpe.IsRunning )
            {
                Text = @"Press 'Enter' when finished. 'Esc' to exit.";
                tboxInput.Clear();
                _presses.Clear();
                StartGarbagePump();
            }

        }

        private void TboxInputLeave( object sender, EventArgs e )
        {
            AppLostFocus( true );
        }

        private void GpFormLeave( object sender, EventArgs e )
        {
            AppLostFocus( true );
        }

        private void GpFormDeactivate( object sender, EventArgs e )
        {
            AppLostFocus( true );
        }

        private void TboxInputKeyPress( object sender, KeyPressEventArgs e )
        {
            ApplicationKeyMonitor( e );
        }

        private void GpFormKeyPress( object sender, KeyPressEventArgs e )
        {
            ApplicationKeyMonitor( e );
        }

        private void GpFormClosing( object sender, FormClosingEventArgs e )
        {
            if ( _gpe != null )
                _gpe.Dispose();
        }
    }
}
